﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UMLSynthesizer.CSharpAnalyzer.Elements;
using UMLSynthesizer.LangAnalyzer.Elements;
using UMLSynthesizer.LangAnalyzer.ExtensionMethods;

namespace UMLSynthesizer.CSharpAnalyzer.Helpers
{
    class CSharpElementContainerHelper
    {
        #region Fields

        IContainerOfElementsBase elementContainerRoot;

        IEnumerable<IClassElement> _containedClasses;
        IEnumerable<IConstructorElement> _containedConstructors;
        IEnumerable<IDelegateElement> _containedDelegates;
        IDestructorElement _containedDestructor;
        IEnumerable<IEnumElement> _containedEnums;
        IEnumerable<IFieldElement> _containedFields;
        IEnumerable<IInterfaceElement> _containedInterfaces;
        IEnumerable<IMethodElement> _containedMethods;
        IEnumerable<INamespaceElement> _containedNamespaces;
        IEnumerable<IPropertyElement> _containedProperties;
        IEnumerable<IStructElement> _containedStructs;

        #endregion

        #region Delegates

        Func<IEnumerable<SyntaxNode>> GetFittingNodes;
        Func<IEnumerable<KeyValuePair<int, SyntaxNode>>> GetNotFittingNodes;

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor for the helper
        /// </summary>
        /// <param name="elementContainerRoot">A referment to the root element container</param>
        /// <param name="getFittingNodes">delegate method for select fitting nodes</param>
        /// <param name="getNotFittingNodes">delegate method for select not fitting nodes</param>
        public CSharpElementContainerHelper(IContainerOfElementsBase elementContainerRoot,
            Func<IEnumerable<SyntaxNode>> getFittingNodes,
            Func<IEnumerable<KeyValuePair<int, SyntaxNode>>> getNotFittingNodes)
        {
            this.elementContainerRoot = elementContainerRoot;
            this.GetFittingNodes = getFittingNodes;
            this.GetNotFittingNodes = getNotFittingNodes;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Get all contained classes
        /// </summary>
        public IEnumerable<IClassElement> ContainedClasses
        {
            get
            {
                if (_containedClasses == null)
                    _containedClasses = new List<IClassElement>(
                        from groupedNodes in
                            (from node in GetFittingNodes()
                             from childNode in node.ChildNodes()
                             where childNode.CSharpKind() == SyntaxKind.ClassDeclaration
                             select childNode as ClassDeclarationSyntax).GroupBy(node =>
                                node.Identifier.ToString())
                        orderby groupedNodes.Key
                        select new CSharpClassElement(groupedNodes, elementContainerRoot));
                return _containedClasses;
            }
        }
        /// <summary>
        /// Get all contained constructors
        /// </summary>
        public IEnumerable<IConstructorElement> ContainedConstructors
        {
            get
            {
                if (_containedConstructors == null)
                    _containedConstructors = new List<IConstructorElement>(
                        from node in GetFittingNodes()
                        from childNode in node.ChildNodes()
                        where childNode.CSharpKind() == SyntaxKind.ConstructorDeclaration
                        select new CSharpConstructorElement(childNode as ConstructorDeclarationSyntax,
                            elementContainerRoot));
                return _containedConstructors;
            }
        }
        /// <summary>
        /// Get all contained delegates
        /// </summary>
        public IEnumerable<IDelegateElement> ContainedDelegates
        {
            get
            {
                if (_containedDelegates == null)
                    _containedDelegates = new List<IDelegateElement>(
                        from node in GetFittingNodes()
                        from childNode in node.ChildNodes()
                        where childNode.CSharpKind() == SyntaxKind.DelegateDeclaration
                        orderby (childNode as DelegateDeclarationSyntax).Identifier.ToString()
                        select new CSharpDelegateElement(childNode as DelegateDeclarationSyntax, elementContainerRoot));
                return _containedDelegates;
            }
        }
        /// <summary>
        /// Get the destructor if exists, null otherwise
        /// </summary>
        public IDestructorElement ContainedDestructor
        {
            get
            {
                if(_containedDestructor == null)
                    _containedDestructor = (from node in GetFittingNodes()
                        from childNode in node.ChildNodes()
                        where childNode.CSharpKind() == SyntaxKind.DestructorDeclaration
                        select new CSharpDestructorElement(childNode as DestructorDeclarationSyntax,
                            elementContainerRoot)).FirstOrDefault();
                return _containedDestructor;
            }
        }
        /// <summary>
        /// Get all contained enums
        /// </summary>
        public IEnumerable<IEnumElement> ContainedEnums
        {
            get
            {
                if (_containedEnums == null)
                    _containedEnums = new List<IEnumElement>(
                        from node in GetFittingNodes()
                        from childNode in node.ChildNodes()
                        where childNode.CSharpKind() == SyntaxKind.EnumDeclaration
                        orderby (childNode as EnumDeclarationSyntax).Identifier.ToString()
                        select new CSharpEnumElement(childNode as EnumDeclarationSyntax, elementContainerRoot));
                return _containedEnums;
            }
        }
        /// <summary>
        /// Get all contained fields
        /// </summary>
        public IEnumerable<IFieldElement> ContainedFields
        {
            get
            {
                if (_containedFields == null)
                    _containedFields = new List<IFieldElement>(
                        from node in GetFittingNodes()
                        from childNode in node.ChildNodes()
                        where childNode.CSharpKind() == SyntaxKind.FieldDeclaration
                        select new CSharpFieldElement((childNode as FieldDeclarationSyntax), elementContainerRoot));
                return _containedFields;
            }
        }
        /// <summary>
        /// Get all contained interfaces
        /// </summary>
        public IEnumerable<IInterfaceElement> ContainedInterfaces
        {
            get
            {
                if (_containedInterfaces == null)
                    _containedInterfaces = new List<IInterfaceElement>(
                        from groupedNodes in
                            (from node in GetFittingNodes()
                             from childNode in node.ChildNodes()
                             where childNode.CSharpKind() == SyntaxKind.InterfaceDeclaration
                             select childNode as InterfaceDeclarationSyntax).GroupBy(node =>
                                node.Identifier.ToString())
                        orderby groupedNodes.Key
                        select new CSharpInterfaceElement(groupedNodes, elementContainerRoot));
                return _containedInterfaces;
            }
        }
        /// <summary>
        /// Get all contained methods
        /// </summary>
        public IEnumerable<IMethodElement> ContainedMethods
        {
            get
            {
                if (_containedMethods == null)
                    _containedMethods = new List<IMethodElement>(
                        from node in GetFittingNodes()
                        from childNode in node.ChildNodes()
                        where childNode.CSharpKind() == SyntaxKind.MethodDeclaration
                        orderby (childNode as MethodDeclarationSyntax).Identifier.ToString()
                        select new CSharpMethodElement(childNode as MethodDeclarationSyntax, elementContainerRoot));
                return _containedMethods;
            }
        }
        /// <summary>
        /// Get all contained namespaces
        /// </summary>
        public IEnumerable<INamespaceElement> ContainedNamespaces
        {
            get
            {
                if (_containedNamespaces == null)
                    _containedNamespaces = new List<INamespaceElement>(
                        from groupedNodes in

                            //select from fitting nodes
                            (from node in GetFittingNodes()
                             from childNode in node.ChildNodes()
                             where childNode.CSharpKind() == SyntaxKind.NamespaceDeclaration
                             select new KeyValuePair<int, NamespaceDeclarationSyntax>(0,
                                 childNode as NamespaceDeclarationSyntax))

                            //select from not fitting nodes
                             .Union
                             (from pairDeepNode in GetNotFittingNodes()
                              where pairDeepNode.Value.CSharpKind() == SyntaxKind.NamespaceDeclaration
                              select new KeyValuePair<int, NamespaceDeclarationSyntax>(pairDeepNode.Key + 1,
                                  pairDeepNode.Value as NamespaceDeclarationSyntax))

                              .GroupBy(pairDeepNode =>
                                  pairDeepNode.Value.Name.ToString().Split(new char[] { '.' }).ElementAt(pairDeepNode.Key))

                        orderby groupedNodes.Key
                        select new CSharpNamespaceElement(groupedNodes, elementContainerRoot));
                return _containedNamespaces;
            }
        }
        /// <summary>
        /// Get all contained properties
        /// </summary>
        public IEnumerable<IPropertyElement> ContainedProperties
        {
            get
            {
                if (_containedProperties == null)
                    _containedProperties = new List<IPropertyElement>(
                        from node in GetFittingNodes()
                        from childNode in node.ChildNodes()
                        where childNode.CSharpKind() == SyntaxKind.PropertyDeclaration
                        orderby (childNode as PropertyDeclarationSyntax).Identifier.ToString()
                        select new CSharpPropertyElement(childNode as PropertyDeclarationSyntax, elementContainerRoot));
                return _containedProperties;
            }
        }
        /// <summary>
        /// Get all contained structs
        /// </summary>
        public IEnumerable<IStructElement> ContainedStructs
        {
            get
            {
                if (_containedStructs == null)
                    _containedStructs = new List<IStructElement>(
                        from groupedNodes in
                            (from node in GetFittingNodes()
                             from childNode in node.ChildNodes()
                             where childNode.CSharpKind() == SyntaxKind.StructDeclaration
                             select childNode as StructDeclarationSyntax).GroupBy(node =>
                                node.Identifier.ToString())
                        orderby groupedNodes.Key
                        select new CSharpStructElement(groupedNodes, elementContainerRoot));
                return _containedStructs;
            }
        }

        #endregion
    }
}
